// AgentOut.cpp

#include "StdAfx.h"

#include "Common/String/StringConvert.h"

#include "Common/FileSystem/FileDir.h"
#include "Common/String/DataConversions.h"
#include "Common/System/Time.h"

#include "Common/7Zip/CopyCoder.h"

#include "Common/Stream/FileStreams.h"

#include "Common/FileSystem/EnumDirectory.h"
#include "Common/Archive/OpenArchive.h"
#include "Common/Archive/UpdateCallback.h"
#include "Common/Archive/UpdatePair.h"

#include "Agent.h"
#include "UpdateCallbackAgent.h"

namespace Common
{
namespace Agent
{

STDMETHODIMP CAgent::SetFolder(IFolderFolder *folder)
{
  _archiveNamePrefix.Empty();
  if (folder == NULL)
  {
    _agentFolder = NULL;
    return S_OK;
  }
  else
  {
    CComPtr<IFolderFolder> archiveFolder = folder;
    CComPtr<IArchiveFolderInternal> archiveFolderInternal;
    HRESULT hRes(archiveFolder->QueryInterface(__uuidof(IArchiveFolderInternal), (void **)&archiveFolderInternal));
	if(S_OK != hRes)
		return hRes;
    hRes = archiveFolderInternal->GetAgentFolder(&_agentFolder);
	if(S_OK != hRes)
		return hRes;
  }

  CStringVector pathParts;
  pathParts.clear();
  CComPtr<IFolderFolder> folderItem = folder;
  if (folderItem != NULL)
    for (;;)
    {
      CComPtr<IFolderFolder> newFolder;
      folderItem->BindToParentFolder(&newFolder);
      if (newFolder == NULL)
        break;

      CComPropVariant prop;
      if (folderItem->GetFolderProperty(kpidName, &prop) == S_OK)
        if (prop.vt == VT_BSTR)
			pathParts.push_front((const wchar_t *)prop.bstrVal);
      folderItem = newFolder;
    }

  for (size_t i = 0; i < pathParts.size(); i++)
  {
    _archiveNamePrefix += pathParts[i];
    _archiveNamePrefix += CHAR_PATH_SEPARATOR;
  }
  return S_OK;
}

STDMETHODIMP CAgent::SetFiles(const wchar_t *folderPrefix,
    const wchar_t **names, UINT32 numNames)
{
  _folderPrefix = folderPrefix;
  _names.clear();
  //_names.reserve(numNames);
  for (UINT32 i = 0; i < numNames; i++)
    _names.push_back(names[i]);
  return S_OK;
}

static HRESULT EnumerateArchiveItems(CAgent *agent,
									const std::shared_ptr<const CProxyFolder> &item,
									const CString &prefix,
									std::vector<FileSystem::CArcItem> &arcItems)
{
  size_t i;
  HRESULT hRes(S_OK);
  arcItems.reserve(arcItems.size() + item->Files.size() + item->Folders.size());
  for (i = 0; i < item->Files.size(); i++)
  {
    const CProxyFile &fileItem = item->Files[i];
    FileSystem::CArcItem ai;
    hRes = agent->GetArc()->GetItemMTime(fileItem.Index, ai.MTime, ai.MTimeDefined);
	if(S_OK != hRes)
		return hRes;

    CComPropVariant property;
    agent->GetArchive()->GetProperty(fileItem.Index, kpidSize, &property);
    ai.SizeDefined = (property.vt != VT_EMPTY);
    if (ai.SizeDefined)
      ai.Size = String::ConvertPropVariantToUInt64(property);
    ai.IsDir = false;
    ai.Name = prefix + fileItem.Name;
    ai.Censored = true; // test it
    ai.IndexInServer = fileItem.Index;
	arcItems.push_back(ai);
  }
  for (i = 0; i < item->Folders.size(); i++)
  {
    const auto &dirItem = item->Folders[i];
    CString fullName = prefix + dirItem->Name;
    if (dirItem->IsLeaf)
    {
      FileSystem::CArcItem ai;
      hRes = agent->GetArc()->GetItemMTime(dirItem->Index, ai.MTime, ai.MTimeDefined);
	  if(S_OK != hRes)
		return hRes;
      ai.IsDir = true;
      ai.SizeDefined = false;
      ai.Name = fullName;
      ai.Censored = true; // test it
      ai.IndexInServer = dirItem->Index;
	  arcItems.push_back(ai);
    }
    hRes = EnumerateArchiveItems(agent, dirItem, fullName + CString(CHAR_PATH_SEPARATOR), arcItems);
	if(S_OK != hRes)
		return hRes;
  }
  return S_OK;
}

struct CAgUpCallbackImp: public Common::Archive::IUpdateProduceCallback
{
	const std::vector<FileSystem::CArcItem> *_arcItems;
	IFolderArchiveUpdateCallback *_callback;
  
	CAgUpCallbackImp(const std::vector<FileSystem::CArcItem> *a,
      IFolderArchiveUpdateCallback *callback): _arcItems(a), _callback(callback) {}
  HRESULT ShowDeleteFile(int arcIndex);
};

HRESULT CAgUpCallbackImp::ShowDeleteFile(int arcIndex)
{
  return _callback->DeleteOperation((*_arcItems)[arcIndex].Name);
}

STDMETHODIMP CAgent::DoOperation(
    Common::Archive::CCodecs *codecs,
    int formatIndex,
    const wchar_t *newArchiveName,
    const BYTE *stateActions,
    const wchar_t *sfxModule,
    IFolderArchiveUpdateCallback *updateCallback100)
{
  if (!CanUpdate())
    return E_NOTIMPL;
  Common::Archive::Update::CActionSet actionSet;
  size_t i;
  for (i = 0; i < Common::Archive::Update::PairState::kNumValues; i++)
    actionSet.StateActions[i] = (Common::Archive::Update::PairAction)stateActions[i];

  FileSystem::CDirItems dirItems;

  {
    CString folderPrefix = _folderPrefix;
    FileSystem::FileName::NormalizeDirPathPrefix(folderPrefix);
    CStringVector errorPaths;
    std::vector<DWORD> errorCodes;
    dirItems.EnumerateDirItems2(folderPrefix, _archiveNamePrefix, _names, errorPaths, errorCodes);
    if (errorCodes.size() > 0)
      return errorCodes.front();
  }

  HRESULT hRes(S_OK);

  CComPtr<IOutArchive> outArchive;
  if (GetArchive())
  {
    hRes = GetArchive()->QueryInterface(__uuidof(IOutArchive), (void **)&outArchive);
	if(S_OK != hRes)
		return hRes;
  }
  else
  {
    if (formatIndex < 0)
      return E_FAIL;
    hRes = codecs->CreateOutArchive(formatIndex, &outArchive);
	if(S_OK != hRes)
		return hRes;
    #ifdef EXTERNAL_CODECS
    {
      CComPtr<ISetCompressCodecsInfo> setCompressCodecsInfo;
      outArchive.QueryInterface(&setCompressCodecsInfo);
      if (setCompressCodecsInfo)
      {
        hRes = setCompressCodecsInfo->SetCompressCodecsInfo(codecs);
		if(S_OK != hRes)
			return hRes;
      }
    }
    #endif

  }

  Common::Archive::FileTimeType fileTimeType;
  UINT32 value;
  hRes = outArchive->GetFileTimeType(&value);
  if(S_OK != hRes)
	return hRes;

  switch(value)
  {
	case Common::Archive::FileTimeType::kWindows:
    case Common::Archive::FileTimeType::kDOS:
    case Common::Archive::FileTimeType::kUnix:
      fileTimeType = Common::Archive::FileTimeType(value);
      break;
    default:
      return E_FAIL;
  }


  std::vector<FileSystem::CArcItem> arcItems;
  if (GetArchive())
  {
    hRes = ReadItems();
	if(S_OK != hRes)
		return hRes;
    EnumerateArchiveItems(this, _proxyArchive->RootFolder, _T(""), arcItems);
  }

  std::vector<Common::Archive::CUpdatePair2> updatePairs2;

  {
	  std::vector<Common::Archive::CUpdatePair> updatePairs;
    GetUpdatePairInfoList(dirItems, arcItems, fileTimeType, updatePairs);
    CAgUpCallbackImp upCallback(&arcItems, updateCallback100);
    UpdateProduce(updatePairs, actionSet, updatePairs2, &upCallback);
  }

  UINT32 numFiles = 0;
  for (i = 0; i < updatePairs2.size(); i++)
    if (updatePairs2[i].NewData)
      numFiles++;
  
  if (updateCallback100)
  {
    hRes = updateCallback100->SetNumFiles(numFiles);
	if(S_OK != hRes)
		return hRes;
  }
  
  CUpdateCallbackAgent updateCallbackAgent;
  updateCallbackAgent.SetCallback(updateCallback100);
  Archive::CArchiveUpdateCallback *updateCallbackSpec = new CComObject<Archive::CArchiveUpdateCallback>;
  updateCallbackSpec->FinalConstruct();
  CComPtr<IArchiveUpdateCallback> updateCallback(updateCallbackSpec );

  updateCallbackSpec->DirItems = &dirItems;
  updateCallbackSpec->ArcItems = &arcItems;
  updateCallbackSpec->UpdatePairs = &updatePairs2;
  updateCallbackSpec->Archive = GetArchive();
  updateCallbackSpec->Callback = &updateCallbackAgent;

  COutFileStream *outStreamSpec = new CComObject<COutFileStream>;
  outStreamSpec->FinalConstruct();
  CComPtr<IOutStream> outStream(outStreamSpec);
  CString archiveName = newArchiveName;
  {
    CString resultPath;
    int pos;
    if (!Common::FileSystem::GetFullPathName(archiveName, resultPath, pos))
      return E_FAIL;
    Common::FileSystem::CreateComplexDirectory(resultPath.Left(pos));
  }
  if (!outStreamSpec->Create(archiveName, true))
  {
    return E_FAIL;
  }
  
  CComPtr<ISetProperties> setProperties;
  if (outArchive.QueryInterface(&setProperties) == S_OK)
  {
    if (m_PropNames.size() == 0)
    {
      hRes = setProperties->SetProperties(0, 0, 0);
	  if(S_OK != hRes)
		return hRes;
    }
    else
    {
		std::vector<const wchar_t *> names;
      for(i = 0; i < m_PropNames.size(); i++)
		  names.push_back((const wchar_t *)m_PropNames[i]);

      CComPropVariant *propValues = new CComPropVariant[m_PropValues.size()];
      try
      {
        for (size_t i = 0; i < m_PropValues.size(); i++)
          propValues[i] = m_PropValues[i];
        hRes = setProperties->SetProperties(&names.front(), propValues, names.size());
		if(S_OK != hRes)
		return hRes;
      }
      catch(...)
      {
        delete []propValues;
        return E_FAIL;
      }
      delete []propValues;
    }
  }
  m_PropNames.clear();
  m_PropValues.clear();

  if (sfxModule != NULL)
  {
    CInFileStream *sfxStreamSpec = new CComObject<CInFileStream>;
	sfxStreamSpec->FinalConstruct();
    CComPtr<IInStream> sfxStream(sfxStreamSpec);
    if (!sfxStreamSpec->Open(sfxModule))
      return E_FAIL;
      // throw "Can't open sfx module";
    hRes = Compress::CopyStream(sfxStream, outStream, NULL);
	if(S_OK != hRes)
		return hRes;
  }

  hRes = outArchive->UpdateItems(outStream, updatePairs2.size(),updateCallback);
  if(S_OK != hRes)
	return hRes;
  return outStreamSpec->Close();
}

STDMETHODIMP CAgent::DoOperation2(
    const wchar_t *newArchiveName,
    const BYTE *stateActions,
    const wchar_t *sfxModule,
    IFolderArchiveUpdateCallback *updateCallback100)
{
  return DoOperation(_codecs, -1, newArchiveName,
    stateActions, sfxModule, updateCallback100);
}

HRESULT CAgent::CommonUpdate(
    const wchar_t *newArchiveName,
    int numUpdateItems,
    IArchiveUpdateCallback *updateCallback)
{
  if (!CanUpdate())
    return E_NOTIMPL;
  CComPtr<IOutArchive> outArchive;
  HRESULT hRes(GetArchive()->QueryInterface(__uuidof(IOutArchive), (void **)&outArchive));
  if(S_OK != hRes)
	return hRes;

  COutFileStream *outStreamSpec = new CComObject<COutFileStream>;
  outStreamSpec->FinalConstruct();
  CComPtr<IOutStream> outStream(outStreamSpec);

  CString archiveName = newArchiveName;
  {
    CString resultPath;
    int pos;
    if (!Common::FileSystem::GetFullPathName(archiveName, resultPath, pos))
      throw 141716;
    Common::FileSystem::CreateComplexDirectory(resultPath.Left(pos));
  }

  /*
  bool isOK = false;
  for (int i = 0; i < (1 << 16); i++)
  {
    resultName = newArchiveName;
    if (i > 0)
    {
      wchar_t s[16];
      ConvertUInt32ToString(i, s);
      resultName += s;
    }
    if (outStreamSpec->Open(realPath))
    {
      isOK = true;
      break;
    }
    if (::GetLastError() != ERROR_FILE_EXISTS)
      return ::GetLastError();
  }
  if (!isOK)
    return ::GetLastError();
  */
  if (!outStreamSpec->Create(archiveName, true))
  {
    return E_FAIL;
  }
  
  hRes = outArchive->UpdateItems(outStream, numUpdateItems, updateCallback);
  if(S_OK != hRes)
		return hRes;
  return outStreamSpec->Close();
}


STDMETHODIMP CAgent::DeleteItems(
    const wchar_t *newArchiveName,
    const UINT32 *indices, UINT32 numItems,
    IFolderArchiveUpdateCallback *updateCallback100)
{
  if (!CanUpdate())
    return E_NOTIMPL;
  CUpdateCallbackAgent updateCallbackAgent;
  updateCallbackAgent.SetCallback(updateCallback100);
  Archive::CArchiveUpdateCallback *updateCallbackSpec = new CComObject<Archive::CArchiveUpdateCallback>;
  updateCallbackSpec->FinalConstruct();
  CComPtr<IArchiveUpdateCallback> updateCallback(updateCallbackSpec);
  
  std::vector<UINT32> realIndices;
  _agentFolder->GetRealIndices(indices, numItems, realIndices);
  std::vector<Common::Archive::CUpdatePair2> updatePairs;
  size_t curIndex = 0;
  UINT32 numItemsInArchive;
  HRESULT hRes(GetArchive()->GetNumberOfItems(&numItemsInArchive));
  if(S_OK != hRes)
		return hRes;
  for (UINT32 i = 0; i < numItemsInArchive; i++)
  {
    if (curIndex < realIndices.size())
      if (realIndices[curIndex] == i)
      {
        curIndex++;
        continue;
      }
    Common::Archive::CUpdatePair2 up2;
    up2.NewData = up2.NewProps = false;
    up2.IsAnti = false; // check it. Maybe it can be undefined
    up2.ArcIndex = i;
	updatePairs.push_back(up2);
  }
  updateCallbackSpec->UpdatePairs = &updatePairs;
  updateCallbackSpec->Archive = GetArchive();
  updateCallbackSpec->Callback = &updateCallbackAgent;
  return CommonUpdate(newArchiveName, updatePairs.size(), updateCallback);
}

HRESULT CAgent::CreateFolder(
    const wchar_t *newArchiveName,
    const wchar_t *folderName,
    IFolderArchiveUpdateCallback *updateCallback100)
{
  if (!CanUpdate())
    return E_NOTIMPL;
  CUpdateCallbackAgent updateCallbackAgent;
  updateCallbackAgent.SetCallback(updateCallback100);
  Archive::CArchiveUpdateCallback *updateCallbackSpec = new CComObject<Archive::CArchiveUpdateCallback>;
  updateCallbackSpec->FinalConstruct();
  CComPtr<IArchiveUpdateCallback> updateCallback(updateCallbackSpec);

  std::vector<Common::Archive::CUpdatePair2> updatePairs;
  UINT32 numItemsInArchive;
  HRESULT hRes(GetArchive()->GetNumberOfItems(&numItemsInArchive));
  if(S_OK != hRes)
		return hRes;
  for (UINT32 i = 0; i < numItemsInArchive; i++)
  {
    Common::Archive::CUpdatePair2 up2;
    up2.NewData = up2.NewProps = false;
    up2.IsAnti = false;  // check it.
    up2.ArcIndex = i;
	updatePairs.push_back(up2);
  }
  Common::Archive::CUpdatePair2 up2;
  up2.NewData = up2.NewProps = true;
  up2.IsAnti = false;
  up2.DirIndex = 0;

  updatePairs.push_back(up2);

  updatePairs.shrink_to_fit();

  FileSystem::CDirItems dirItems;
  FileSystem::CDirItems::PtrItems di = std::make_shared<FileSystem::CDirItem>() ;

  di->Attributes = FILE_ATTRIBUTE_DIRECTORY;
  di->Size = 0;
  di->Name = _agentFolder->_proxyFolderItem->GetFullPathPrefix() + folderName;

  di->ResetTimeToCurrent();
  
  dirItems.Items.push_back(di);

  updateCallbackSpec->Callback = &updateCallbackAgent;
  updateCallbackSpec->DirItems = &dirItems;
  updateCallbackSpec->UpdatePairs = &updatePairs;
  updateCallbackSpec->Archive = GetArchive();
  return CommonUpdate(newArchiveName, updatePairs.size(), updateCallback);
}


HRESULT CAgent::RenameItem(
    const wchar_t *newArchiveName,
    const UINT32 *indices, UINT32 numItems,
    const wchar_t *newItemName,
    IFolderArchiveUpdateCallback *updateCallback100)
{
  if (!CanUpdate())
    return E_NOTIMPL;
  if (numItems != 1)
    return E_INVALIDARG;
  CUpdateCallbackAgent updateCallbackAgent;
  updateCallbackAgent.SetCallback(updateCallback100);
  Archive::CArchiveUpdateCallback *updateCallbackSpec = new CComObject<Archive::CArchiveUpdateCallback>;
  updateCallbackSpec->FinalConstruct();
  CComPtr<IArchiveUpdateCallback> updateCallback(updateCallbackSpec);
  
  std::vector<UINT32> realIndices;
  _agentFolder->GetRealIndices(indices, numItems, realIndices);

  CString fullPrefix = _agentFolder->GetFullPathPrefixPlusPrefix(indices[0]);
  CString oldItemPath = fullPrefix + _agentFolder->GetName(indices[0]);
  CString newItemPath = fullPrefix + newItemName;

  std::vector<Common::Archive::CUpdatePair2> updatePairs;
  CStringVector newNames;

  size_t curIndex = 0;
  UINT32 numItemsInArchive;
  HRESULT hRes(GetArchive()->GetNumberOfItems(&numItemsInArchive));
  if(S_OK != hRes)
		return hRes;
  for (UINT32 i = 0; i < numItemsInArchive; i++)
  {
    if (curIndex < realIndices.size())
      if (realIndices[curIndex] == i)
      {
        Common::Archive::CUpdatePair2 up2;
        up2.NewData = false;
        up2.NewProps = true;
        hRes = GetArc()->IsItemAnti(i, up2.IsAnti);
		if(S_OK != hRes)
			return hRes;
        up2.ArcIndex = i;

        CString oldFullPath;
        hRes = GetArc()->GetItemPath(i, oldFullPath);
		if(S_OK != hRes)
			return hRes;

        if (oldItemPath.CompareNoCase(oldFullPath.Left(oldItemPath.GetLength())) != 0)
          return E_INVALIDARG;

		newNames.push_back(newItemPath + oldFullPath.Mid(oldItemPath.GetLength()));
		up2.NewNameIndex = newNames.size() + 1;
        updatePairs.push_back(up2);
        curIndex++;
        continue;
      }
    Common::Archive::CUpdatePair2 up2;
    up2.NewData = up2.NewProps = false;
    up2.IsAnti = false;
    up2.ArcIndex = i;
    updatePairs.push_back(up2);
  }
  updateCallbackSpec->Callback = &updateCallbackAgent;
  updateCallbackSpec->UpdatePairs = &updatePairs;
  updateCallbackSpec->NewNames = &newNames;
  updateCallbackSpec->Archive = GetArchive();
  return CommonUpdate(newArchiveName, updatePairs.size(), updateCallback);
}

STDMETHODIMP CAgent::SetProperties(const wchar_t **names,
    const PROPVARIANT *values, INT32 numProperties)
{
  m_PropNames.clear();
  m_PropValues.clear();
  for (int i = 0; i < numProperties; i++)
  {
	  m_PropNames.push_back(names[i]);
	  m_PropValues.push_back(values[i]);
  }
  return S_OK;
}

}}